﻿using Newtonsoft.Json.Linq;
using System.Collections;
using System.Reflection;
using System.Reflection.Metadata;

namespace MetadataDesignerLibrary
{
    /// <summary>
    /// Правило языка CIL, у которого реализованы и запись, и чтение
    /// </summary>
    /// <typeparam name="THandle"></typeparam>
    public abstract class ILRule<THandle> : ILRule
        where THandle : struct
    {
        /// <summary>
        /// Конструктор правила языка CIL, который в дальнейшем будет записан с помощью <see cref="Write(MetadataDesigner)"/>
        /// </summary>
        public ILRule()
        {
        }

        /// <summary>
        /// Конструктор правила языка CIL, который строит правило путём чтения
        /// </summary>
        /// <param name="reader"></param>
        public ILRule(MetadataReader reader)
        {
        }

        /// <summary>
        /// Запись правила в <see cref="MetadataDesigner"/>
        /// </summary>
        /// <param name="designer"></param>
        /// <returns></returns>
        public abstract THandle Write(MetadataDesigner designer);

        internal override ValueType InternalWrite(MetadataDesigner designer)
        {
            return Write(designer);
        }
    }

    public abstract class ILRule
    {
        internal abstract ValueType InternalWrite(MetadataDesigner designer);

        /// <summary>
        /// Запись множества правил в <see cref="MetadataDesigner"/>
        /// </summary>
        /// <param name="designer"></param>
        /// <param name="rules"></param>
        public static void WriteRange(
            MetadataDesigner designer,
            params ILRule[] rules)
        {
            foreach (ILRule rule in rules)
            {
                rule.InternalWrite(designer);
            }
        }

        /// <summary>
        /// Получение <see cref="JObject"/> из правила
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        public JObject ToJson(MetadataReader reader)
        {
            return (JObject)ToJson(new HashSet<int>(), false, this, reader);
        }

        private static JToken ToJson(HashSet<int> hash, bool isLoop, object source, MetadataReader reader)
        {
            if (source == null)
            {
                return null;
            }
            else if (source is ILRule rule)
            {
                JObject result = new JObject
                {
                    { "<Type>", rule.GetType().Name }
                };
                foreach (PropertyInfo propertyInfo in rule.GetType().GetProperties())
                {
                    ILInfoAttribute attr = (ILInfoAttribute)propertyInfo.GetCustomAttribute(typeof(ILInfoAttribute));
                    if (attr != null)
                    {
                        if (isLoop && !attr.Unique)
                            continue;
                        string name = propertyInfo.Name;
                        object value = propertyInfo.GetValue(rule);
                        switch (attr.ParseMethod)
                        {
                            case ILInfoParseMethod.Default:
                                {
                                    result.Add(name, ToJson(hash, isLoop, value, reader));
                                }
                                break;

                            case ILInfoParseMethod.AsString:
                                {
                                    result.Add(name, ToJson(hash, isLoop, value.ToString(), reader));
                                }
                                break;

                            case ILInfoParseMethod.AsEntityHandle:
                                {
                                    try
                                    {
                                        result.Add(name, ToJson(hash, isLoop, (EntityHandle)(dynamic)value, reader));
                                    }
                                    catch
                                    {
                                        // skip
                                    }
                                }
                                break;

                            case ILInfoParseMethod.AsEntityHandleCollection:
                                {
                                    try
                                    {
                                        JArray array = new JArray();
                                        foreach (EntityHandle item in (dynamic)value)
                                            array.Add(ToJson(hash, isLoop, item, reader));
                                        result.Add(name, array);
                                    }
                                    catch
                                    {
                                        // skip
                                    }
                                }
                                break;

                            default:
                                throw new NotSupportedException($"{attr.ParseMethod} не реализован!");
                        }
                    }
                }
                return result;
            }
            else if (source is EntityHandle entityHandle)
            {
                int entityHash = entityHandle.GetHashCode();
                bool loop = isLoop || hash.Contains(entityHash);
                hash.Add(entityHash);
                return ToJson(hash, loop, ParseEntityHandle(reader, entityHandle), reader);
            }
            else if (source is string @string)
            {
                return JToken.FromObject(@string);
            }
            else if (source is IEnumerable enumerable)
            {
                JArray array = new JArray();
                foreach (object item in enumerable)
                    array.Add(ToJson(hash, isLoop, item, reader));
                return array;
            }
            else if (source is Enum @enum)
            {
                return JToken.FromObject(@enum.ToString());
            }
            else
            {
                return JToken.FromObject(source);
            }
        }

        /// <summary>
        /// Приведение <see cref="EntityHandle"/> к правилу <see cref="ILRule"/>
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="entityHandle"></param>
        /// <returns></returns>
        public static ILRule ParseEntityHandle(MetadataReader reader, EntityHandle entityHandle)
        {
            if (entityHandle.IsNil)
                return null;
            return entityHandle.Kind switch
            {
                HandleKind.ModuleDefinition => new ModuleDefinitionRule(reader),
                HandleKind.TypeReference => new TypeReferenceRule(reader, (TypeReferenceHandle)entityHandle),
                HandleKind.TypeDefinition => new TypeDefinitionRule(reader, (TypeDefinitionHandle)entityHandle),
                HandleKind.FieldDefinition => new FieldDefinitionRule(reader, (FieldDefinitionHandle)entityHandle),
                HandleKind.MethodDefinition => new MethodDefinitionRule(reader, (MethodDefinitionHandle)entityHandle),
                HandleKind.Parameter => new ParameterRule(reader, (ParameterHandle)entityHandle),
                HandleKind.InterfaceImplementation => new InterfaceImplementationRule(reader, (InterfaceImplementationHandle)entityHandle),
                HandleKind.MemberReference => new MemberReferenceRule(reader, (MemberReferenceHandle)entityHandle),
                HandleKind.Constant => new ConstantRule(reader, (ConstantHandle)entityHandle),
                HandleKind.CustomAttribute => new CustomAttributeRule(reader, (CustomAttributeHandle)entityHandle),
                HandleKind.DeclarativeSecurityAttribute => new DeclarativeSecurityAttributeRule(reader, (DeclarativeSecurityAttributeHandle)entityHandle),
                HandleKind.StandaloneSignature => new StandaloneSignatureRule(reader, (StandaloneSignatureHandle)entityHandle),
                HandleKind.EventDefinition => new EventDefinitionRule(reader, (EventDefinitionHandle)entityHandle),
                HandleKind.PropertyDefinition => new PropertyDefinitionRule(reader, (PropertyDefinitionHandle)entityHandle),
                HandleKind.MethodImplementation => new MethodImplementationRule(reader, (MethodImplementationHandle)entityHandle),
                HandleKind.ModuleReference => new ModuleReferenceRule(reader, (ModuleReferenceHandle)entityHandle),
                HandleKind.TypeSpecification => new TypeSpecificationRule(reader, (TypeSpecificationHandle)entityHandle),
                HandleKind.AssemblyDefinition => new AssemblyDefinitionRule(reader),
                HandleKind.AssemblyReference => new AssemblyReferenceRule(reader, (AssemblyReferenceHandle)entityHandle),
                HandleKind.AssemblyFile => new AssemblyFileRule(reader, (AssemblyFileHandle)entityHandle),
                HandleKind.ExportedType => new ExportedTypeRule(reader, (ExportedTypeHandle)entityHandle),
                HandleKind.ManifestResource => new ManifestResourceRule(reader, (ManifestResourceHandle)entityHandle),
                HandleKind.GenericParameter => new GenericParameterRule(reader, (GenericParameterHandle)entityHandle),
                HandleKind.MethodSpecification => new MethodSpecificationRule(reader, (MethodSpecificationHandle)entityHandle),
                HandleKind.GenericParameterConstraint => new GenericParameterConstraintRule(reader, (GenericParameterConstraintHandle)entityHandle),
                HandleKind.Document => new DocumentRule(reader, (DocumentHandle)entityHandle),
                HandleKind.MethodDebugInformation => new MethodDebugInformationRule(reader, (MethodDebugInformationHandle)entityHandle),
                HandleKind.LocalScope => new LocalScopeRule(reader, (LocalScopeHandle)entityHandle),
                HandleKind.LocalVariable => new LocalVariableRule(reader, (LocalVariableHandle)entityHandle),
                HandleKind.LocalConstant => new LocalConstantRule(reader, (LocalConstantHandle)entityHandle),
                HandleKind.ImportScope => new ImportScopeRule(reader, (ImportScopeHandle)entityHandle),
                HandleKind.CustomDebugInformation => new CustomDebugInformationRule(reader, (CustomDebugInformationHandle)entityHandle),
                HandleKind.UserString => null,
                HandleKind.Blob => null,
                HandleKind.Guid => null,
                HandleKind.String => null,
                HandleKind.NamespaceDefinition => null,
                _ => null,
            };
        }
    }
}